package com.yunxi.module.system.service.dept.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.google.common.annotations.VisibleForTesting;
import com.yunxi.framework.common.enums.CommonStatusEnum;
import com.yunxi.framework.common.exception.util.ServiceExceptionUtil;
import com.yunxi.framework.common.util.collection.CollectionUtils;
import com.yunxi.framework.common.util.object.BeanUtils;
import com.yunxi.framework.datapermission.core.annotation.DataPermission;
import com.yunxi.module.system.enums.constant.ErrorCodeConstants;
import com.yunxi.module.system.mapper.dept.DeptMapper;
import com.yunxi.module.system.model.dept.DeptDO;
import com.yunxi.module.system.redis.RedisKeyConstants;
import com.yunxi.module.system.service.dept.DeptService;
import com.yunxi.module.system.vo.admin.dept.dept.DeptListReqVO;
import com.yunxi.module.system.vo.admin.dept.dept.DeptSaveReqVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;

import static com.yunxi.framework.common.util.collection.CollectionUtils.convertSet;

/**
 * <p>
 * 部门 Service 实现类
 * </p>
 *
 * @author lidy
 * @since 2024-05-08
 */
@Service
@Slf4j
public class DeptServiceImpl implements DeptService {

    @Resource
    private DeptMapper deptMapper;

    /**
     * 从缓存中获得所有子部门
     *
     * @param id 父部门编号
     * @return 子部门列表
     */
    @Override
    @DataPermission(enable = false) // 禁用数据权限，避免建立不正确的缓存
    @Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, key = "#id")
    public Set<Long> getChildDeptIdListFromCache(Long id) {
        List<DeptDO> children = getChildDeptList(id);
        return convertSet(children, DeptDO::getId);
    }

    /**
     * 获得指定部门的所有子部门
     *
     * @param id 部门编号
     * @return 子部门列表
     */
    @Override
    public List<DeptDO> getChildDeptList(Long id) {
        List<DeptDO> children = new LinkedList<>();
        // 遍历每一层
        Collection<Long> parentIds = Collections.singleton(id);
        for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下，存在死循环
            // 查询当前层，所有的子部门
            List<DeptDO> depts = deptMapper.selectListByParentId(parentIds);
            // 1. 如果没有子部门，则结束遍历
            if (CollUtil.isEmpty(depts)) {
                break;
            }
            // 2. 如果有子部门，继续遍历
            children.addAll(depts);
            parentIds = convertSet(depts, DeptDO::getId);
        }
        return children;
    }

    /**
     * 根据部门编号获取部门信息
     *
     * @param deptId 部门编号
     * @return 部门信息
     */
    @Override
    public DeptDO getDept(Long deptId) {
        return deptMapper.selectById(deptId);
    }

    /**
     * 获得部门列表
     *
     * @param reqVO 筛选条件
     * @return 部门列表
     */
    @Override
    public List<DeptDO> getDeptList(DeptListReqVO reqVO) {
        List<DeptDO> deptDOS = deptMapper.selectList(reqVO);
        deptDOS.sort(Comparator.comparing(DeptDO::getSort));
        return deptDOS;
    }

    /**
     * 获得指定编号的部门 map
     *
     * @param deptIds 部门编号
     * @return 部门 map
     */
    @Override
    public Map<Long, DeptDO> getDeptMap(Collection<Long> deptIds) {
        List<DeptDO> list = getDeptList(deptIds);
        return CollectionUtils.convertMap(list, DeptDO::getId);
    }

    /**
     * 获得部门信息集合
     *
     * @param deptIds 部门编号数组
     * @return 部门信息集合
     */
    @Override
    public List<DeptDO> getDeptList(Collection<Long> deptIds) {
        // 如果 ID 为空，返回空集合
        if (CollUtil.isEmpty(deptIds)) {
            return Collections.emptyList();
        }
        return deptMapper.selectBatchIds(deptIds);
    }

    /**
     * 校验部门是否处于开启状态
     * 以下情况视为无效：
     * 1. 部门编号不存在
     * 2. 部门被禁用
     *
     * @param deptIds 部门编号集合
     */
    @Override
    public void validateDeptList(Collection<Long> deptIds) {
        // 部门编号集合为空，不执行逻辑
        if (CollUtil.isEmpty(deptIds)) {
            return;
        }
        // 获得部门 map
        Map<Long, DeptDO> deptMap = getDeptMap(deptIds);
        // 校验
        deptIds.forEach(deptId -> {
            DeptDO deptDO = deptMap.get(deptId);
            // 部门不存在
            if (deptDO == null) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_NOT_FOUND);
            }
            // 部门禁用
            if (!CommonStatusEnum.ENABLE.getStatus().equals(deptDO.getStatus())) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_NOT_ENABLE, deptDO.getName());
            }
        });
    }

    /**
     * 创建部门
     * allEntries 清空素有缓存，因为操作一个部门，涉及到多个缓存
     *
     * @param createReqVO 部门信息
     * @return 部门编号
     */
    @Override
    @CacheEvict(value = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, allEntries = true)
    public Long createDept(DeptSaveReqVO createReqVO) {
        if (createReqVO.getParentId() == null) {
            createReqVO.setParentId(DeptDO.PARENT_ID_ROOT);
        }

        // 校验父部门的有效性
        validateParentDept(null, createReqVO.getParentId());

        // 校验部门名称的唯一性
        validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName());

        // 插入数据库
        DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class);
        deptMapper.insert(dept);

        return dept.getId();
    }

    /**
     * 更新部门
     * allEntries 清空素有缓存，因为操作一个部门，涉及到多个缓存
     *
     * @param updateReqVO 部门信息
     */
    @Override
    @CacheEvict(value = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, allEntries = true)
    public void updateDept(DeptSaveReqVO updateReqVO) {
        if (updateReqVO.getParentId() == null) {
            updateReqVO.setParentId(DeptDO.PARENT_ID_ROOT);
        }

        // 校验是否已存在
        validateDeptExists(updateReqVO.getId());

        // 校验父部门的有效性
        validateParentDept(updateReqVO.getId(), updateReqVO.getParentId());

        // 校验部门名称的唯一性
        validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());

        // 更新部门
        DeptDO dept = BeanUtils.toBean(updateReqVO, DeptDO.class);
        deptMapper.updateById(dept);
    }

    /**
     * 删除部门
     * allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
     *
     * @param deptId 部门编号
     */
    @Override
    @CacheEvict(value = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, allEntries = true)
    public void deleteDept(Long deptId) {
        // 校验是否存在
        validateDeptExists(deptId);
        // 校验是否有子部门
        if (deptMapper.selectCountByParentId(deptId) > 0) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_EXITS_CHILDREN);
        }

        // 删除部门
        deptMapper.deleteById(deptId);
    }

    /**
     * 校验部门是否存在
     *
     * @param deptId 部门编号
     */
    @VisibleForTesting
    private void validateDeptExists(Long deptId) {
        if (deptId == null) {
            return;
        }
        DeptDO deptDO = deptMapper.selectById(deptId);
        if (deptDO == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_NOT_FOUND);
        }
    }

    /**
     * 校验部门名称的唯一性
     *
     * @param deptId   部门编号
     * @param parentId 父部门编号
     * @param name     部门名称
     */
    @VisibleForTesting
    private void validateDeptNameUnique(Long deptId, Long parentId, String name) {
        DeptDO deptDO = deptMapper.selectByParentIdAndName(parentId, name);
        if (deptDO == null) {
            return;
        }

        // 如果部门编号为空，说明不用比较是否为相同 ID 的部门
        if (deptId == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_NAME_DUPLICATE);
        }
        if (ObjectUtil.notEqual(deptDO.getId(), deptId)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_NAME_DUPLICATE);
        }
    }

    /**
     * 校验父部门的有效性
     *
     * @param deptId   当前部门编号
     * @param parentId 父部门编号
     */
    @VisibleForTesting
    private void validateParentDept(Long deptId, Long parentId) {
        // 父部门编号为空或者父部门为根节点，不用校验
        if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) {
            return;
        }

        // 1. 不能设置自己为父部门
        if (Objects.equals(deptId, parentId)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_PARENT_ERROR);
        }

        // 2. 父部门不存在
        DeptDO parentDept = deptMapper.selectById(parentId);
        if (parentDept == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_PARENT_NOT_EXITS);
        }

        // 3. 地柜校验父部门，如果父部门是自己的子部门，则报错，避免形成环路
        if (deptId == null) {
            // 部门编号为空，说明新增，不需要考虑环路
            return;
        }
        for (int i = 0; i < Short.MAX_VALUE; i++) {
            // 3.1 校验环路
            parentId = parentDept.getParentId();
            if (Objects.equals(deptId, parentId)) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_PARENT_IS_CHILD);
            }

            // 3.2 继续递归下一级父部门
            if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) {
                break;
            }
            parentDept = deptMapper.selectById(parentId);
            if (parentDept == null) {
                break;
            }
        }
    }
}
